package com.darren.archive.utils;

import java.util.Date;

import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.DatatypeConverter;

import com.darren.archive.exception.TokenException;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import com.alibaba.fastjson.JSONObject;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.MalformedJwtException;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.SignatureException;

public class JWTUtil {

	private static Logger logger = LogManager.getLogger(JWTUtil.class);

	/**
	 * Access Token 的密钥
	 */
	private static String ACCESS_SECRET =  "AB1E4F3EA534567890A2ECE3245AFD";

	/**
	 * Refresh Token 的密钥
	 */
	private static String REFRESH_SECRET = "BAA2277AE3BDE436A40E2AC5F4AB72D5";

	/**
	 * Access Token 的有效期时长，默认15分钟
	 */
	private static long ACCESS_MAX_AGE = 900000L;

	/**
	 * Refresh Token 的有效期时长，默认半小时
	 */
	private static long REFRESH_MAX_AGE = 1800000L;

	/**
	 * Access Token 的加密方式
	 */
	private static SignatureAlgorithm ACCESS_ALGORITHM;

	/**
	 * Refresh Token 的加密方式
	 */
	private static SignatureAlgorithm REFRESH_ALGORITHM;

	/**
	 * jwt签发者
	 */
	private static String ISSUER = "Archive";

	/**
	 * 设置jwt签发者
	 * @param issuer
	 */
	public static void setIssuer(String issuer){
		ISSUER = issuer;
	}

	/**
	 * 设置Access 有效期时长
	 * @param accessMaxAge
	 */
	public static void setAccessExpired(long accessMaxAge){
		ACCESS_MAX_AGE = accessMaxAge;
	}

	/**
	 * 获取Access 有效期时长
	 * @return
	 */
	public static long getAccessExpired(){
		return ACCESS_MAX_AGE;
	}

	/**
	 * 设置Refresh 有效期时长
	 * @param refreshMaxAge
	 */
	public static void setRefreshExpired(long refreshMaxAge){
		REFRESH_MAX_AGE = refreshMaxAge;
	}

	/**
	 * 获取Refresh 有效期时长
	 * @return
	 */
	public static long getRefreshExpired(){
		return REFRESH_MAX_AGE;
	}

	/**
	 * 设置Access Token 的密钥
	 * @param accessSecret
	 */
	public static void setAccessSecret(String accessSecret){
		ACCESS_SECRET = accessSecret;
	}

	/**
	 * 设置Refresh Token 的密钥
	 * @param refreshSecret
	 */
	public static void setRefreshSecret(String refreshSecret){
		REFRESH_SECRET = refreshSecret;
	}

	/**
	 * 设置加密方式
	 */
	static{
		ACCESS_ALGORITHM = SignatureAlgorithm.HS512;
		REFRESH_ALGORITHM = SignatureAlgorithm.HS384;
	}

	/**
	 * 生成Access Token
	 * @param payload
	 * @return
	 */
	public static <T> String signAccessJWT(T payload){
		return signAccessJWT(payload,ACCESS_MAX_AGE);
	}

	/**
	 * 生成Access Token
	 * @param payload
	 * @param maxAge 0 表未永不过期
	 * @return
	 */
	public static <T> String signAccessJWT(T payload,long maxAge) {
		long expTime = maxAge >= 0 ? maxAge: ACCESS_MAX_AGE;
		return signJWT(payload,ACCESS_ALGORITHM,ACCESS_SECRET,expTime);
	}

	/**
	 * 生成Refresh Token
	 * @param payload
	 * @return
	 */
	public static <T> String signRefreshJWT(T payload){
		return signRefreshJWT(payload,ACCESS_MAX_AGE);
	}

	/**
	 * 生成Refresh Token
	 * @param payload
	 * @param maxAge 0 表未永不过期
	 * @return
	 */
	public static <T> String signRefreshJWT(T payload,long maxAge) {
		long expTime = maxAge >= 0 ? maxAge: REFRESH_MAX_AGE;
		return signJWT(payload,REFRESH_ALGORITHM,REFRESH_SECRET,expTime);
	}

	/**
	 * 生成Token
	 * @param payload
	 * @param signatureAlgorithm
	 * @param secret
	 * @param maxAge
	 * @return
	 */
	private static <T> String signJWT(T payload,SignatureAlgorithm signatureAlgorithm,String secret,long maxAge) {
		long nowMillis = System.currentTimeMillis();
		Date now = new Date(nowMillis);
		byte[] secretBytes = DatatypeConverter.parseBase64Binary(secret);
		SecretKey signingKey = new SecretKeySpec(secretBytes, signatureAlgorithm.getJcaName());
		String payloadString = JSONObject.toJSONString(payload);
		// 添加构成JWT的参数
		JwtBuilder builder =  Jwts.builder().setHeaderParam("typ", "JWT")
				.setIssuedAt(now) // iat: jwt的签发时间
				.setSubject(payloadString) // 主题 sub(Subject)：代表这个JWT的主体，即它的所有人，这个是一个json格式的字符串，可以存放什么userid，roldid之类的，作为什么用户的唯一标志,也差不多是个人的一些信息
				.setIssuer(ISSUER) // jwt签发者
				.signWith(signatureAlgorithm, signingKey); // 估计是第三段密钥
		// 添加Token过期时间
		if (maxAge > 0) {
			// 过期时间
			long expMillis = nowMillis + maxAge;
			// 现在是什么时间
			Date exp = new Date(expMillis);
			// 系统时间之前的token都是不可以被承认的
			builder.setExpiration(exp)//exp: jwt的过期时间，这个过期时间必须要大于签发时间
					.setNotBefore(now);//nbf: 定义在什么时间之前，该jwt都是不可用的
		}
		// 生成JWT字符串
		return builder.compact();
	}


	/**
	 * 解密Access jwt
	 * @param jwt
	 * @param clazz
	 * @return
	 */
	public static <T> T unSignAccessJWT(String jwt, Class<T> clazz){
		return unSignJWT(jwt,clazz,ACCESS_ALGORITHM,ACCESS_SECRET);
	}

	/**
	 * 解密Refresh jwt
	 * @param jwt
	 * @param clazz
	 * @return
	 */
	public static <T> T unSignRefreshJWT(String jwt, Class<T> clazz){
		return unSignJWT(jwt,clazz,REFRESH_ALGORITHM,REFRESH_SECRET);
	}

	/**
	 * 解密 jwt
	 * @param jwtString
	 * @param clazz
	 * @param signatureAlgorithm
	 * @param secret
	 * @return
	 */
	private static <T> T unSignJWT(String jwtString,Class<T> clazz,SignatureAlgorithm signatureAlgorithm,String secret){
		logger.info(jwtString);
		byte[] encodedKey = DatatypeConverter.parseBase64Binary(secret);
		SecretKey key = new SecretKeySpec(encodedKey, signatureAlgorithm.getJcaName());// 根据给定的字节数组使用AES加密算法构造一个密钥，使用 encodedKey中的始于且包含 0 到前 leng 个字节这是当然是所有。（后面的文章中马上回推出讲解Java加密和解密的一些算法）
		try {
			Claims claims = Jwts.parser() // 得到DefaultJwtParser
					.setSigningKey(key) // 设置签名的秘钥
					.parseClaimsJws(jwtString).getBody();// 设置需要解析的jwt
			String payloadString = claims.getSubject();
			return JSONObject.parseObject(payloadString,clazz);
		}catch (IllegalArgumentException illegalArgumentException) {
			logger.error(illegalArgumentException.getMessage(), illegalArgumentException);
			throw new TokenException(TokenStatus.FORMAT_ERROR);
		}catch (MalformedJwtException malformedJwtException) {
			logger.error(malformedJwtException.getMessage(), malformedJwtException);
			throw new TokenException(TokenStatus.FORMAT_ERROR);
		}catch (SignatureException signatureException) {
			logger.error(signatureException.getMessage(), signatureException);
			throw new TokenException(TokenStatus.INVALID_SIGNATURE);
		} catch (ExpiredJwtException expiredJwtException) {
			logger.error(expiredJwtException.getMessage(), expiredJwtException);
			throw new TokenException(TokenStatus.EXPIRED);
		}catch (Exception exception) {
			logger.error(exception.getMessage(), exception);
			throw new TokenException(TokenStatus.UNKNOWN);
		}
	}

	/**
	 * JWT Token 的状态
	 * @author Darren Zhou
	 */
	public static enum TokenStatus {
		AVAILABLE("有效的token。",0),
		TOKEN_NULL_OR_EMPTY("token 不可以为空。",-1),
		FORMAT_ERROR("token格式错误！",-2),
		INVALID_SIGNATURE("无效token签名。",-3),
		INVALID_TOKEN("无效的token。",-4),
		EXPIRED("token已过期。", -5),
		UNKNOWN("未知错误。", -9999);

		// 成员变量
		private String message;
		private int code;
		// 构造方法
		private TokenStatus(String message, int code) {
			this.message = message;
			this.code = code;
		}
		// 普通方法
		public static String getMessage(int code) {
			for (TokenStatus status : TokenStatus.values()) {
				if (status.getCode() == code) {
					return status.message;
				}
			}
			return null;
		}
		// get set 方法
		public String getMessage() {
			return message;
		}
		public void setMessage(String message) {
			this.message = message;
		}
		public int getCode() {
			return code;
		}
		public void setCode(int code) {
			this.code = code;
		}
	}
}

